#!/usr/bin/python
# TODO. Integrate this with geeklist_tool.py
"""Parses a BGG maths trade geeklist to extract codes and whatnot
"""

__author__ = "Bruce Murphy"

import codecs
import collections
import csv
import datetime
import dateutil.parser
import dateutil.tz
import re
import sys
import xml.etree.ElementTree as ET
import StringIO

import geeklist_tool


def MTCode(itemid, objectname):
  """Generates the alpha-numeric code based on the game name.

  This originally duplicated the old trade-code name generation logic but
  now does a waterfill among non-short words.

  This code has no built-in protection against distinct games creating
  duplicate codes.
  """
  length = 5
  itemid = str(itemid)
  if type(itemid) == unicode:
    itemid = itemid.encode('utf-8')
  name = objectname.lower()
  # get rid of any unicode silliness.
  if type(name) == unicode:
    name = name.encode('utf-8')
  # remove punctuation
  name = re.sub(r"[^A-Za-z0-9 ]", "", name)
  # remove multiple spaces
  name = re.sub(r"  +", " ", name).strip()
  # split at spaces
  name_parts = name.split()
  # count words in name
  num_words = len(name_parts)

  shortwords = [ "of", "to", "in", "it", "is", "be", "as", "at", "so",
                 "by", "or", "on", "do", "if", "an", "the", "and", "das" ]

  use_old_codes = False

  if use_old_codes:
    code = ""
    for word in name_parts:
      if (num_words < (length + 1)):
        if word in shortwords:
          prefix_length = 1
        else:
          prefix_length = length + 1 - num_words
      else:
        prefix_length = 1
      code = code + word[0:prefix_length]
  else:
    # new trade code generator. This does a waterfill among the name
    # words, capping shortwords at only a single letter.

    # TODO. Give priority to important annotations such as edition name.
    prefix_lengths = [0] * len(name_parts)
    length_remaining = length
    changed = True
    while length_remaining > 0 and changed:
      changed = False
      for i in range(len(name_parts)):
        if prefix_lengths[i] == 0:
          prefix_lengths[i] += 1
          length_remaining -= 1
          changed = True
        else:
          if (name_parts[i] not in shortwords and
              prefix_lengths[i] < len(name_parts[i])):
            prefix_lengths[i] += 1
            length_remaining -= 1
            changed = True

    code = ""
    for i in range(len(prefix_lengths)):
      code = code + name_parts[i][0:prefix_lengths[i]]

  code = (code + "X" * length)[0:length]
  return itemid + "-" + code.upper()


def BodyIsSold(bodytext):
  """Returns true if the body text indicates an item has been withdrawn."""
  if bodytext.startswith("[-]"):
    return True

  # TODO. Check for a lot of text under strikeout.
  return False


def GetMTCodeFromComment(item):
  "Finds an item code generated by mr_lunch and returns it, or an empty string."
  for child in item:
    if child.tag != "comment":
      continue
    if child.attrib["username"] not in [ "mr_lunch", "thepackrat" ]:
      continue

    m = re.search(r"([0-9]+-.{5})", child.text)
    if m:
      return m.group(1)

  return ""


def MathsTradeItems(xmlroot, write_codes=False):
  """Parses the maths trade geeklist and returns the verifier data file."""
  iobuf = StringIO.StringIO()
  csvout = csv.writer(iobuf, dialect='excel')

  games_by_code = collections.defaultdict(set)

  # TODO: do this automatically in advance.
  # These are hard-coded for the 2015 apr/may sydney trade.
  mtcode_overrides = {
    "Olympus" : "OLYM1",
    "Descent: Journeys in the Dark (Second Edition)": "DJIT2",
    'Space Hulk: Death Angel \xe2\x80\x93 The Card Game \xe2\x80\x93 Tyranid Enemy Pack': "SHDA2",
    "Dominion: Intrigue": "DOMI2"
    }

  max_written = 500

  csvout.writerow(["mtcode", "username", "objectname"])
  for i, child in enumerate(xmlroot):
    if child.tag != "item":
      continue

    # support for historical (third party) trade code generation.
    # item_code = GetMTCodeFromComment(child)
    # new_item_code = MTCode(child.attrib["id"], child.attrib["objectname"])
    # if item_code != new_item_code:
    #   csvout.writerow(["# codes differ old: %s new %s" % (item_code, new_item_code)])

    game_name = child.attrib["objectname"].encode('utf-8')
    generic_code = MTCode("000", game_name)

    if game_name in mtcode_overrides:
      code_game_name = mtcode_overrides[game_name]
    else:
      code_game_name = game_name

    item_code = MTCode(child.attrib["id"], code_game_name)

    # TODO: Make adding codes a two-pass process so that we can add
    # disambiguation in case of duplicates.
    games_by_code[MTCode("000", code_game_name)].add(game_name)

    if not item_code:
      csvout.writerow(["# missing code for item %d" % i])
      continue

    is_withdrawn = False
    if BodyIsSold(geeklist_tool.GetItemBody(child)):
      csvout.writerow(["# item is withdrawn"])
      item_code = "#" + item_code
      is_withdrawn =  True

    if write_codes and max_written > 1:
      # add verification that these haven't changed, also.
      existing_code = GetMTCodeFromComment(child)
      if not existing_code and not is_withdrawn:
        geeklist_tool.PostGeeklistComment(child.attrib["id"],
                                          "[b]%s[/b]" % item_code)
        max_written -= 1

    row = [item_code, child.attrib["username"].encode('utf-8'), child.attrib["objectname"].encode('utf-8')]
    csvout.writerow(row)

  for code in games_by_code:
    if len(games_by_code[code]) > 1:
      csvout.writerow([("# duplicate games on code %s: " % code) +
                       " ".join(games_by_code[code]) ])
  return iobuf.getvalue()

def ItemsThumbedReport(items, username):
  "Provides a report on which data items are actually useful"
  pass

def main(argv):

  # maths code testing short cuts everything because of the noise
  # on the command line.
  if len(argv) > 1 and argv[0] == "mtcode":
    print MTCode("0000000", " ".join(argv[1:]))
    sys.exit(0)

  if len(argv) < 1:
    xml_filename = "../data/2015-may-sydney-trade-189961.xml"
  else:
    xml_filename = argv[0]

  # SWITCH here on reports for all items for user X, repeated items
  # for a particular code, etc.
  if len(argv) < 2:
    bgg_user = ""
  else:
    bgg_user = argv[1]

  geeklist_data = geeklist_tool.ReadAndParseXML(xml_filename)
  #geeklist_tool.BasicXMLReport(geeklist_data)

  if not bgg_user:
    sys.sydout = codecs.getwriter('utf-8')(sys.stdout)
    print MathsTradeItems(geeklist_data, True),
  else:
    print ItemsThumbedReport(geeklist_data)


if __name__ == "__main__":
  main(sys.argv[1:])
